﻿// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Drawing;

namespace System.Windows.Forms.PropertyGridInternal;

/// <summary>
///  The help (description) pane optionally shown at the bottom of the <see cref="PropertyGrid"/>.
/// </summary>
/// <remarks>
///  <para>
///   <see cref="PropertyGrid.HelpVisible"/> controls the visibility of this control.
///  </para>
/// </remarks>
internal partial class HelpPane : PropertyGrid.SnappableControl
{
    private readonly Label _titleLabel;
    private readonly Label _descriptionLabel;
    private string? _fullDescription;

    private int _lineHeight;
    private bool _needUpdateUIWithFont = true;

    private const int LogicalDefaultWidth = 0;
    private const int LogicalDefaultHeight = 59;
    private const int MinimumLines = 2;

    private int _borderSize;

    private Rectangle _lastClientRectangle = Rectangle.Empty;

    internal HelpPane(PropertyGrid owner) : base(owner)
    {
        using SuspendLayoutScope scope = new(this, performLayout: false);

        _titleLabel = new()
        {
            UseMnemonic = false,
            Cursor = Cursors.Default
        };

        _descriptionLabel = new()
        {
            AutoEllipsis = true,
            Cursor = Cursors.Default
        };

        UpdateTextRenderingEngine();

        Controls.Add(_titleLabel);
        Controls.Add(_descriptionLabel);

        Size = new(LogicalToDeviceUnits(LogicalDefaultWidth), LogicalToDeviceUnits(LogicalDefaultHeight));

        Text = SR.PropertyGridHelpPaneTitle;
        SetStyle(ControlStyles.Selectable, false);
    }

    private protected override void InitializeControl() => ScaleConstants();

    public virtual int Lines
    {
        get
        {
            UpdateUIWithFont();
            return Height / _lineHeight;
        }
        set
        {
            UpdateUIWithFont();
            Size = new(Width, 1 + value * _lineHeight);
        }
    }

    public override int GetOptimalHeight(int width)
    {
        UpdateUIWithFont();

        // Compute optimal label height as one line only.
        int height = _titleLabel.Size.Height;

        // Do this to avoid getting parented to the Parking window.
        if (OwnerPropertyGrid.IsHandleCreated && !IsHandleCreated)
        {
            CreateControl();
        }

        // Compute optimal text height.
        bool isScalingRequirementMet = ScaleHelper.IsScalingRequirementMet;
        using Graphics g = _descriptionLabel.CreateGraphicsInternal();
        SizeF sizef = PropertyGrid.MeasureTextHelper.MeasureText(OwnerPropertyGrid, g, _titleLabel.Text, Font, width);
        Size size = Size.Ceiling(sizef);
        int padding = isScalingRequirementMet ? LogicalToDeviceUnits(2) : 2;
        height += (size.Height * 2) + padding;
        return Math.Max(height + 2 * padding, isScalingRequirementMet ? LogicalToDeviceUnits(LogicalDefaultHeight) : LogicalDefaultHeight);
    }

    internal virtual void LayoutWindow()
    {
    }

    protected override void OnFontChanged(EventArgs e)
    {
        _needUpdateUIWithFont = true;
        PerformLayout();
        base.OnFontChanged(e);
    }

    protected override void OnLayout(LayoutEventArgs e)
    {
        UpdateUIWithFont();
        SetLabelBounds();
        _descriptionLabel.Text = _fullDescription;
        _descriptionLabel.AccessibleName = _fullDescription; // Don't crop the description for accessibility clients
        base.OnLayout(e);
    }

    protected override void OnResize(EventArgs e)
    {
        Rectangle currentClientRectangle = ClientRectangle;
        if (!_lastClientRectangle.IsEmpty && currentClientRectangle.Width > _lastClientRectangle.Width)
        {
            Invalidate(new Rectangle(
                _lastClientRectangle.Width - 1,
                0,
                currentClientRectangle.Width - _lastClientRectangle.Width + 1,
                _lastClientRectangle.Height));
        }

        if (ScaleHelper.IsScalingRequirementMet)
        {
            int oldLineHeight = _lineHeight;
            _lineHeight = Font.Height + LogicalToDeviceUnits(2);
            if (oldLineHeight != _lineHeight)
            {
                _titleLabel.Location = new(_borderSize, _borderSize);
                _descriptionLabel.Location = new(_borderSize, _borderSize + _lineHeight);

                SetLabelBounds();
            }
        }

        _lastClientRectangle = currentClientRectangle;
        base.OnResize(e);
    }

    private void SetLabelBounds()
    {
        Size size = ClientSize;

        // If the client size is 0, setting this to a negative number will force an extra layout.
        size.Width = Math.Max(0, size.Width - 2 * _borderSize);
        size.Height = Math.Max(0, size.Height - 2 * _borderSize);

        _titleLabel.SetBounds(
            _titleLabel.Top,
            _titleLabel.Left,
            size.Width,
            Math.Min(_lineHeight, size.Height),
            BoundsSpecified.Size);

        _descriptionLabel.SetBounds(
            _descriptionLabel.Top,
            _descriptionLabel.Left,
            size.Width,
            Math.Max(0, size.Height - _lineHeight - (ScaleHelper.IsScalingRequirementMet ? LogicalToDeviceUnits(1) : 1)),
            BoundsSpecified.Size);
    }

    protected override void OnHandleCreated(EventArgs e)
    {
        base.OnHandleCreated(e);
        UpdateUIWithFont();
    }

    protected override void RescaleConstantsForDpi(int deviceDpiOld, int deviceDpiNew)
    {
        base.RescaleConstantsForDpi(deviceDpiOld, deviceDpiNew);
        ScaleConstants();
    }

    private void ScaleConstants()
    {
        const int LogicalBorderSize = 3;
        _borderSize = LogicalToDeviceUnits(LogicalBorderSize);
    }

    public virtual void SetDescription(string? title, string? description)
    {
        if (_descriptionLabel.Text != title)
        {
            _titleLabel.Text = title;
        }

        if (description != _fullDescription)
        {
            _fullDescription = description;
            _descriptionLabel.Text = _fullDescription;

            // Don't crop the description for accessibility clients.
            _descriptionLabel.AccessibleName = _fullDescription;
        }
    }

    public override int SnapHeightRequest(int newHeight)
    {
        UpdateUIWithFont();
        int lines = Math.Max(MinimumLines, newHeight / _lineHeight);
        return 1 + lines * _lineHeight;
    }

    /// <inheritdoc />
    protected override AccessibleObject CreateAccessibilityInstance() => new HelpPaneAccessibleObject(this, OwnerPropertyGrid);

    /// <inheritdoc />
    internal override bool SupportsUiaProviders => true;

    internal void UpdateTextRenderingEngine()
    {
        _titleLabel.UseCompatibleTextRendering = OwnerPropertyGrid.UseCompatibleTextRendering;
        _descriptionLabel.UseCompatibleTextRendering = OwnerPropertyGrid.UseCompatibleTextRendering;
    }

    private void UpdateUIWithFont()
    {
        if (IsHandleCreated && _needUpdateUIWithFont)
        {
            // Some fonts throw because Bold is not a valid option for them. Fail gracefully.
            try
            {
                _titleLabel.Font = new(Font, FontStyle.Bold);
            }
            catch (Exception e) when (!e.IsCriticalException())
            {
            }

            _lineHeight = Font.Height + 2;
            _titleLabel.Location = new(_borderSize, _borderSize);
            _descriptionLabel.Location = new(_borderSize, _borderSize + _lineHeight);

            _needUpdateUIWithFont = false;
            PerformLayout();
        }
    }
}
